home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1994 February: Tool Chest / Dev.CD Feb 94.toast / New System Software Extensions / PlainTalk™ Speech Technologies / Text To Speech / Programming Stuff / Examples / WannaSpeech / WannaSpeech.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-09-15  |  12.8 KB  |  458 lines  |  [TEXT/KAHL]

  1. /* this file contains all the routines related to the speech capabilities of
  2.    WannaSpeech.
  3.    Written by:    Guillermo A. Ortiz        AppleSoft Developer Technical Support
  4.    Date:        08/04/93
  5.    
  6.    08/18/93 - 
  7. */
  8. #include "App.h"            /* Get the application includes/typedefs, etc.    */
  9. #include "App.defs.h"        /* Get various application definitions.            */
  10. #include "App.protos.h"        /* Get the prototypes for application.            */
  11. #include "WannaSpeech.h"    /* Get speech stuff trying to minimize files changed */
  12.  
  13. Boolean gCanTalk;
  14.  
  15. /* variables used in calls to set voice channel parameters */
  16. Fixed speechRate, pitchValue;
  17. OSType textMode;
  18.  
  19. /* returns an instance of the voice picker component if it is available.
  20.    it is used in the menu fixing routine as a boolean to test the existance
  21.    of the component and also to prepare for presenting the dialog.
  22. */
  23. ComponentInstance OpenStdTTS(void)
  24. {
  25. Component ttsComponent;
  26. ComponentInstance ttsCInstance;
  27. ComponentDescription ttsDesc;
  28. short resID, oldResID;
  29. Handle ttsRH;
  30. Point where = {0,0};
  31. OSErr err = componentDontRegister;
  32.  
  33.     ttsDesc.componentType = 'stts';
  34.     ttsDesc.componentSubType = 0;
  35.     ttsDesc.componentManufacturer = 'appl';
  36.     ttsDesc.componentFlags = 0;
  37.     ttsDesc.componentFlagsMask = 0;
  38.  
  39.     if ( gCanTalk ) {
  40.       if ((ttsComponent = FindNextComponent(nil, &ttsDesc)) )
  41.         return ttsCInstance = OpenComponent(ttsComponent);
  42.     }
  43. }
  44.  
  45. /* When the user choses Select Voice this routine invokes the Voice Picker
  46.    and changes the voice info struct in the document if necessary.
  47. */
  48. void SetVoice(FileRecHndl frHndl)
  49. {
  50. ComponentInstance voicePicker;
  51. StdTTSParams speechParams; /* struct used when calling standard TTS component */
  52. WindowPtr w = FrontWindow();
  53. short result = 1;
  54. ControlHandle    ctl;
  55. Str255            pstr;
  56. TEHandle        te1;
  57.  
  58.     if (voicePicker = OpenStdTTS() ) {
  59.     speechParams = (*frHndl)->d.doc.docSpeech;
  60.     if ( StdSpeechDialog(voicePicker, &speechParams, nil) ) { /* user changed the voice */
  61.       /* allophones are supported by galatea if the new voice is not galatean then
  62.          warn the user
  63.       */
  64.       if ( speechParams.curVoice.voice.creator != 'gala' ) {
  65.         CNum2Ctl(w, kModeName, &ctl);
  66.         CTEGetPStr(ctl, pstr);
  67.         if (! pcmp(pstr, "\pAllophones")) {
  68.           if ( result = HCenteredAlert(128,w, nil) != ok ) 
  69.             goto bail;
  70.           
  71.           CNum2Ctl(w, kPhonemeCtl, &ctl);
  72.           te1 = (TEHandle)GetCRefCon(ctl);
  73.           if ((*te1)->hText) {
  74.             TESetSelect(0, 32767, te1);
  75.             TEDelete(te1);
  76.           }
  77.  
  78.             SetTECtlText(w, frHndl, kModeName, "\pPhonemes");
  79.         }
  80.       }
  81.       (*frHndl)->d.doc.docSpeech = speechParams;
  82.       SetTECtlText(w, frHndl, kVoiceName, (*frHndl)->d.doc.docSpeech.curVoice.name);
  83.     }
  84. bail:
  85.     CloseComponent(voicePicker);
  86.     }
  87. }
  88.  
  89. /* This routine selects the input method for the channel depending on
  90.    the text edit field that is active.
  91. */
  92. void DoSayIt(FileRecHndl frHndl)
  93. {
  94. WindowPtr        w;
  95. ControlHandle    ctl;
  96. TEHandle        te1, te2;
  97. Str255            pstr;
  98.  
  99.     w = FrontWindow();
  100.     CNum2Ctl(w, kTextCtl, &ctl);
  101.     te1 = (TEHandle)GetCRefCon(ctl);
  102.     te2 = CTEFindActive(nil);
  103.     if (te1 == te2) {
  104.       textMode  = 'TEXT';
  105.     }
  106.     else { /* could be phonemes or allophones */
  107.       textMode  = 'PHON';
  108.       CNum2Ctl(w, kModeName, &ctl);
  109.       CTEGetPStr(ctl, pstr);
  110.       if (! pcmp(pstr, "\pAllophones"))
  111.         textMode = 'ALLO';
  112.     }
  113.     SayText(frHndl, te2);
  114. }
  115.  
  116. /* pass nil if the window is active */
  117. void CreatePhonemesList(WindowPtr wind)
  118. {
  119. PhonemeDescriptor **phonemeSymbols;
  120. ControlHandle    ctl;
  121. TEHandle        te1;
  122. long    phonemesLength;
  123. OSErr err;
  124. SpeechChannel    sChannel;
  125. WindowPtr w;
  126. short i;
  127. short saveEnd;
  128. TextStyle ts;
  129. StdTTSParams speechParams;
  130.  
  131.     if (wind)
  132.       w = wind;
  133.     else
  134.       w = FrontWindow();
  135.     CNum2Ctl(w, kPhonemeLst, &ctl);
  136.     te1 = (TEHandle)GetCRefCon(ctl);
  137.  
  138.     if (!(err = NewSpeechChannel(&speechParams.curVoice.voice, &sChannel)) ) { /* nil means give me the current channel */
  139.  
  140.       if ( ! (GetSpeechInfo(sChannel, soPhonemeSymbols, &phonemeSymbols) )) {
  141.       
  142.         if ((*te1)->hText) {
  143.           TESetSelect(0, 32767, te1);
  144.           TEDelete(te1);
  145.         }
  146.         for (i=0; i < (*phonemeSymbols)->phonemeCount; i++) {
  147.           TEInsert(&((*phonemeSymbols)->thePhonemes[i].phStr[1]), 
  148.                      (*phonemeSymbols)->thePhonemes[i].phStr[0], te1);     /* insert phoneme */
  149.           TEInsert("  ", 1, te1);                                         /* space */
  150.           TEInsert(&((*phonemeSymbols)->thePhonemes[i].exampleStr[1]), 
  151.                      (*phonemeSymbols)->thePhonemes[i].exampleStr[0], te1); /* example */
  152.           saveEnd = (*te1)->selEnd;
  153.           (*te1)->selStart = saveEnd - (*phonemeSymbols)->thePhonemes[i].exampleStr[0] + (*phonemeSymbols)->thePhonemes[i].hiliteStart;
  154.           (*te1)->selEnd = saveEnd - (*phonemeSymbols)->thePhonemes[i].exampleStr[0] + (*phonemeSymbols)->thePhonemes[i].hiliteEnd;
  155.           
  156.           ts.tsFace = bold;
  157.           TESetStyle(doFace, &ts, false, te1);
  158.           (*te1)->selStart = (*te1)->selEnd = saveEnd;
  159. //          TESetSelect(saveEnd, saveEnd, te1);
  160.           ts.tsFace = 0;
  161.           TESetStyle(doFace, &ts, false, te1);
  162.     
  163.           TEInsert("\r", 1, te1);                                        /* and return */
  164.         }
  165.         (*te1)->selStart = (*te1)->selEnd;
  166.         TEDelete(te1);
  167.         
  168.         DisposeHandle((Handle)phonemeSymbols);
  169.         TECalText(te1);                /* Force all resizing to make tectl happy. */
  170.         CTEAdjustTEBottom(te1);
  171.         CTEAdjustScrollValues(te1);
  172.       }
  173.       err = DisposeSpeechChannel(sChannel);
  174.     }
  175. }
  176.  
  177. /* When it is necessary to change the in a TE control this is the code that does it.
  178. */
  179. void SetTECtlText(WindowPtr window, FileRecHndl frHndl, short ctlNumber, Str255 pstr)
  180. {
  181. ControlHandle    ctl;
  182. TEHandle        te1;
  183.  
  184.     CNum2Ctl(window, ctlNumber, &ctl);
  185.     UseControlStyle(ctl);
  186.     CTESetPStr(ctl, pstr);
  187.     UseControlStyle(nil);
  188. }
  189.  
  190. void DoMakePhonemes(FileRecHndl frHndl)
  191. {
  192. Handle phonemesBuffer;
  193. ControlHandle    ctl;
  194. TEHandle        te1, te2;
  195. long    phonemesLength;
  196. OSErr err;
  197. SpeechChannel    sChannel;
  198. WindowPtr w;
  199. CursHandle        hWatchCursor;
  200.  
  201.     hWatchCursor = GetCursor(watchCursor);
  202.     HLock((Handle)hWatchCursor);
  203.  
  204.     w = FrontWindow();
  205.  
  206.     CNum2Ctl(w, kTextCtl, &ctl);
  207.     te1 = (TEHandle)GetCRefCon(ctl);
  208.  
  209.     CNum2Ctl(w, kPhonemeCtl, &ctl);
  210.     te2 = (TEHandle)GetCRefCon(ctl);
  211.  
  212.     if (!(    phonemesBuffer = NewHandleClear(((*te1) -> teLength)*2) )) {
  213.       NewDocumentWindow(nil, 'ALRT', false);
  214.       return;
  215.     }
  216.  
  217.     HLock((Handle) te1);
  218.     HLock((Handle) (*te1)->hText);
  219.  
  220.  
  221.     SetCursor( *(hWatchCursor));
  222.  
  223.     if (!(err = NewSpeechChannel(&((*frHndl)->d.doc.docSpeech.curVoice.voice), &sChannel)) ) { /* nil means give me the current channel */
  224.  
  225.       if (err = TextToPhonemes(sChannel, *((*te1) -> hText), (*te1) -> teLength, phonemesBuffer, &phonemesLength) ) {
  226.         DebugStr("\pTextToPhonemes failed");
  227.         goto bail;
  228.       }
  229.       
  230.       SetTECtlText(w, frHndl, kModeName, "\pPhonemes");
  231.       SetHandleSize(phonemesBuffer, phonemesLength);
  232.       phonemesBuffer = CTESwapText(te2, phonemesBuffer, nil, true);
  233.       
  234.       err = DisposeSpeechChannel(sChannel);
  235.     }
  236. bail:
  237.     InitCursor();
  238.     HUnlock((Handle) (*te1)->hText);
  239.     HUnlock((Handle) te1);
  240.     DisposeHandle(phonemesBuffer);
  241.     
  242.     CTESetSelect(0, (*te2) -> teLength, te2);
  243.     CTEActivate(true, te2);
  244.     HUnlock((Handle)hWatchCursor);
  245.  
  246. }
  247.  
  248. void DoMakeAllophones(FileRecHndl frHndl) /* GALA voices only */
  249. {
  250. Handle allophonesBuffer;
  251. ControlHandle    ctl;
  252. TEHandle        te1, te2;
  253. long    allophonesLength;
  254. OSErr err;
  255. SpeechChannel    sChannel;
  256. WindowPtr w;
  257. CursHandle        hWatchCursor;
  258.  
  259.     hWatchCursor = GetCursor(watchCursor);
  260.     HLock((Handle)hWatchCursor);
  261.  
  262.  
  263.     w = FrontWindow();
  264.  
  265.     CNum2Ctl(w, kTextCtl, &ctl);
  266.     te1 = (TEHandle)GetCRefCon(ctl);
  267.  
  268.     CNum2Ctl(w, kPhonemeCtl, &ctl);
  269.     te2 = (TEHandle)GetCRefCon(ctl);
  270.  
  271.     if (!(    allophonesBuffer = NewHandleClear(((*te1) -> teLength)*2) )) {
  272.       NewDocumentWindow(nil, 'ALRT', false);
  273.       return;
  274.     }
  275.  
  276.     HLock((Handle) te1);
  277.     HLock((Handle) (*te1)->hText);
  278.  
  279.     SetCursor( *(hWatchCursor));
  280.  
  281.     if (!(err = NewSpeechChannel(&((*frHndl)->d.doc.docSpeech.curVoice.voice), &sChannel)) ) { /* nil means give me the current channel */
  282.  
  283.       if (err = TextToAllophones(sChannel, *((*te1) -> hText), (*te1) -> teLength, allophonesBuffer, &allophonesLength) ) {
  284.         DebugStr("\pTextToallophones failed");
  285.         goto bail;
  286.       }
  287.       
  288.       InitCursor();
  289.       SetTECtlText(w, frHndl, kModeName, "\pAllophones");
  290.       SetHandleSize(allophonesBuffer, allophonesLength);
  291.       allophonesBuffer = CTESwapText(te2, allophonesBuffer, nil, true);
  292.       
  293.       err = DisposeSpeechChannel(sChannel);
  294.     }
  295. bail:
  296.     InitCursor();
  297.     HUnlock((Handle) (*te1)->hText);
  298.     HUnlock((Handle) te1);
  299.     DisposeHandle(allophonesBuffer);
  300.     
  301.     CTESetSelect(0, (*te2) -> teLength, te2);
  302.     CTEActivate(true, te2);
  303.     HUnlock((Handle)hWatchCursor);
  304. }
  305.  
  306. /* this little routine uses Gestalt to check for the presence of
  307.    the text to speech manager.
  308. */
  309. Boolean SpeechAvailable(void)
  310. {
  311. OSErr err;
  312. long result;
  313.  
  314.     err = Gestalt(gestaltSpeechAttr, &result);
  315.     if ( (err) || (!(result &(1<<gestaltSpeechMgrPresent)))) {
  316.       DebugStr("\pSpeech Manager is not available");
  317.       return false;
  318.     }
  319.     else
  320.       return true;
  321. }
  322.  
  323. pascal void MyWordCallback(SpeechChannel sChannel, WordLimitsPtr wLP, long wordPos, short wordLen)
  324. /* We will use the word call back to hilite the next word to be spoken. 
  325.    The problem is that this proc is called at interrupt time and can not
  326.    use something that moves memory, so here we only save the information and
  327.    let the "while speechbusy loop do the actual hiliting.
  328. */
  329. {
  330.     wLP -> hilite = true;        /* flag that word needs hiliting */
  331.     wLP -> wordStart = wordPos;
  332.     wLP -> wordEnd = wordPos + wordLen;
  333. }
  334.  
  335. OSErr SayText(FileRecHndl frHndl, TEHandle teH)
  336. /*     - Calls SpeakText.
  337.     - Opens a speech channel,
  338.     - holds until done
  339.     - hilites the word being spoken
  340.     - checks for command period
  341.     - disposes of the channel.
  342. */
  343. {
  344.     SpeechChannel    sChannel;
  345.     KeyMap            kMap;
  346.     TEHandle        oldActive;
  347.     OSErr            err = noErr;            /* optimism abounds */
  348.     WordLimits        wLims;
  349.     GTXtndData myData = {'gala','inpt', 'ALLO'};
  350.     CursHandle        hWatchCursor;
  351.  
  352.     hWatchCursor = GetCursor(watchCursor);
  353.     HLock((Handle)hWatchCursor);
  354.  
  355.  
  356.     if ( ! SpeechAvailable() ) return;
  357.     HLock((Handle) teH);
  358.     HLock((Handle) (*teH)->hText);
  359.  
  360.     SetCursor( *(hWatchCursor));
  361.  
  362.     if (!(err = NewSpeechChannel(&((*frHndl)->d.doc.docSpeech.curVoice.voice), &sChannel)) ) { /* nil means give me the current channel */
  363.       SetSpeechInfo(sChannel, soRefCon, &wLims);
  364.       SetSpeechInfo(sChannel, soWordCallBack, MyWordCallback); /* does not get called for phonemes */
  365.       
  366.       SetSpeechInfo(sChannel, 'rate', &((*frHndl)->d.doc.docSpeech.rate));
  367.       SetSpeechInfo(sChannel, 'pmod', &((*frHndl)->d.doc.docSpeech.modulation));
  368.       SetSpeechInfo(sChannel, 'pbas', &((*frHndl)->d.doc.docSpeech.pitch));
  369.       
  370.       if ( textMode == 'ALLO' ) // Allophones are set via the extended command mechanism, only galatea voices support this mode
  371.         SetSpeechInfo(sChannel, 'xtnd', &myData);
  372.       else 
  373.         SetSpeechInfo(sChannel, soInputMode, &textMode);
  374.       /* need to activate to let user see the highlited words as they
  375.          are spoken.
  376.       */
  377.       
  378.       if (!(err = SpeakText(sChannel, *((*teH) -> hText), (*teH) -> teLength))) {
  379.         while (SpeechBusy() > 0) { /* Need to wait until all stops */
  380.         
  381.           if ( wLims.hilite) {  /* first we check if word being spoken needs hiliting */
  382.             CTESetSelect(wLims.wordStart, wLims.wordEnd, teH);
  383.             wLims.hilite = false;  /* do it only once */
  384.           }
  385.           GetKeys(kMap);
  386.           if ((kMap[1] == 0x808000) ){ /* user is tired of the long message */
  387.               err = StopSpeech(sChannel);
  388.               break;
  389.           }
  390.         }
  391.       }
  392.       
  393.       InitCursor();
  394.       
  395.       SetSpeechInfo(sChannel, soWordCallBack, nil);
  396.       err = DisposeSpeechChannel(sChannel);
  397.     }
  398.     HUnlock((Handle) (*teH)->hText);    /* release handles */                
  399.     HUnlock((Handle) teH);
  400.     HUnlock((Handle)hWatchCursor);
  401.     return err;
  402. }
  403.  
  404. void InitDocSpeech(FileRecHndl frHndl)
  405. {
  406. SpeechChannel sChannel; 
  407. StdTTSParams speechParams;
  408.  
  409.     if (gCanTalk) { /* make sure we can talk */
  410.       if ( GetVoiceDescription(nil, &speechParams.curVoice, sizeof(VoiceDescription)) ) 
  411.           goto bail;
  412.  
  413.       if (NewSpeechChannel(&(speechParams.curVoice.voice), &sChannel) )  /* try voice in record    */
  414.           goto bail;
  415.  
  416.       GetSpeechInfo(sChannel, 'rate', &speechParams.rate);
  417.       GetSpeechInfo(sChannel, 'pbas', &speechParams.pitch);
  418.       GetSpeechInfo(sChannel, 'pmod', &speechParams.modulation);
  419.       DisposeSpeechChannel(sChannel);
  420.       (*frHndl)->d.doc.docSpeech = speechParams;
  421.       (*frHndl)->d.doc.docSpeech.message = (char *)"\pPlease select a voice:";
  422.       (*frHndl)->d.doc.docSpeech.flags = 0;
  423.       return;
  424.     }
  425. bail:    
  426.     NewDocumentWindow(nil, 'ALRT', false);
  427.     (*frHndl)->d.doc.docSpeech.flags = -1;
  428.     return;
  429. }
  430.  
  431. pascal OSErr TextToAllophones(SpeechChannel channel, Ptr textBuf, long textBytes, Handle allophoneBuf, long *allophoneBytes)
  432. {
  433. OSErr err = paramErr;         /* very optimistic */
  434. GTXtndConvertData convertAllo; 
  435. GTXtndData myData;
  436.  
  437.     /* set up struct for GetSpeechInfo call */
  438.     myData.synthID = soGalaSynthID;
  439.     myData.selector = 'xtnd';
  440.     myData.info2 = (long) &convertAllo;
  441.     
  442.     /* set up struct which calls and passes info to convert to allophones */
  443.     convertAllo.synthID = soGalaSynthID;
  444.     convertAllo.selector = soGalaConvertToAllo;
  445.     convertAllo.inputBuffer = textBuf;
  446.     convertAllo.inputLen = textBytes;
  447.     convertAllo.controlFlags = 0;
  448.     convertAllo.outputBuffer = allophoneBuf;
  449.     convertAllo.outputLen = 0;
  450.  
  451.     if ( !(err = GetSpeechInfo(channel, 'xtnd', &convertAllo) )) {
  452.       *allophoneBytes = convertAllo.outputLen;
  453.     }
  454.     
  455. return err;
  456. }
  457.  
  458.